package Intermediate_algorithm.Other;

import org.junit.Test;

import java.util.*;

/*
任务调度器
给你一个用字符数组tasks 表示的 CPU 需要执行的任务列表，用字母 A 到 Z 表示，以及一个冷却时间 n。
每个周期或时间间隔允许完成一项任务。任务可以按任何顺序完成，但有一个限制：两个 相同种类 的任务之间必须有长度为n 的冷却时间。
返回完成所有任务所需要的 最短时间间隔。

示例 1：
输入：tasks = ["A","A","A","B","B","B"], n = 2
输出：8
解释：
在完成任务 A 之后，你必须等待两个间隔。对任务 B 来说也是一样。在第 3 个间隔，A 和 B 都不能完成，所以你需要待命。
在第 4 个间隔，由于已经经过了 2 个间隔，你可以再次执行 A 任务。
示例 2：
输入：tasks = ["A","C","A","B","D","B"], n = 1
输出：6
解释：一种可能的序列是：A -> B -> C -> D -> A -> B。
由于冷却间隔为 1，你可以在完成另一个任务后重复执行这个任务。
示例 3：
输入：tasks = ["A","A","A","B","B","B"], n = 3
输出：10
解释：一种可能的序列为：A -> B -> idle -> idle -> A -> B -> idle -> idle -> A -> B。
只有两种任务类型，A 和 B，需要被 3 个间隔分割。这导致重复执行这些任务的间隔当中有两次待命状态。

提示：
1 <= tasks.length <= 104
tasks[i] 是大写英文字母
0 <= n <= 100
相关标签
Java
作者：LeetCode
链接：https://leetcode.cn/leetbook/read/top-interview-questions-medium/xwvaot/
 */
public class _04任务调度器 {
    
    @Test
    public void test() {
        System.out.println(leastInterval(new char[]{'A', 'A', 'A', 'B', 'B', 'B'}, 3));
    }

    //WA
    public int leastInterval(char[] tasks, int n) {
        if (n == 0)
            return tasks.length;
        HashMap<Character, Integer> map = new HashMap<>();
        for (char c : tasks) {
            map.put(c, map.getOrDefault(c, 0) + 1);
        }
        int res = 0;
        while (!map.isEmpty()){
            Set<Map.Entry<Character, Integer>> entries = map.entrySet();
            int size = map.size();
            Iterator<Map.Entry<Character, Integer>> iterator = entries.iterator();
            while (iterator.hasNext()){
                Map.Entry<Character, Integer> entry = iterator.next();
                entry.setValue(entry.getValue() - 1);
                res++;
                if (entry.getValue() == 0){
                    iterator.remove();
                }
            }
            if (!map.isEmpty() && size - 1 < n){
                res += n - size + 1;
            }
        }
        return res;
    }

    //官解：方法一：模拟
    /*
    作者：力扣官方题解
    链接：https://leetcode.cn/problems/task-scheduler/solutions/509687/ren-wu-diao-du-qi-by-leetcode-solution-ur9w/
     */
    class Solution {
        public int leastInterval(char[] tasks, int n) {
            Map<Character, Integer> freq = new HashMap<Character, Integer>();
            for (char ch : tasks) {
                freq.put(ch, freq.getOrDefault(ch, 0) + 1);
            }

            // 任务种类数
            int m = freq.size();
            List<Integer> nextValid = new ArrayList<Integer>();
            List<Integer> rest = new ArrayList<Integer>();
            Set<Map.Entry<Character, Integer>> entrySet = freq.entrySet();
            for (Map.Entry<Character, Integer> entry : entrySet) {
                int value = entry.getValue();
                nextValid.add(1);
                rest.add(value);
            }

            int time = 0;
            for (int i = 0; i < tasks.length; ++i) {
                ++time;
                int minNextValid = Integer.MAX_VALUE;
                for (int j = 0; j < m; ++j) {
                    if (rest.get(j) != 0) {
                        minNextValid = Math.min(minNextValid, nextValid.get(j));
                    }
                }
                time = Math.max(time, minNextValid);
                int best = -1;
                for (int j = 0; j < m; ++j) {
                    if (rest.get(j) != 0 && nextValid.get(j) <= time) {
                        if (best == -1 || rest.get(j) > rest.get(best)) {
                            best = j;
                        }
                    }
                }
                nextValid.set(best, time + n + 1);
                rest.set(best, rest.get(best) - 1);
            }

            return time;
        }
    }

    //他解：贪心
    //ps: 大佬牛逼
    public int leastInterval2(char[] tasks, int n) {
        //统计每个任务出现的次数，找到出现次数最多的任务
        int[] hash = new int[26];
        for(int i = 0; i < tasks.length; ++i) {
            hash[tasks[i] - 'A'] += 1;
        }
        Arrays.sort(hash);
        //因为相同元素必须有n个冷却时间，假设A出现3次，n = 2，任务要执行完，至少形成AXX AXX A序列（X看作预占位置）
        //该序列长度为
        int minLen = (n+1) *  (hash[25] - 1) + 1;

        //此时为了尽量利用X所预占的空间（贪心）使得整个执行序列长度尽量小，将剩余任务往X预占的空间插入
        //剩余的任务次数有两种情况：
        //1.与A出现次数相同，比如B任务最优插入结果是ABX ABX AB，中间还剩两个空位，当前序列长度+1
        //2.比A出现次数少，若还有X，则按序插入X位置，比如C出现两次，形成ABC ABC AB的序列
        //直到X预占位置还没插满，剩余元素逐个放入X位置就满足冷却时间至少为n
        for(int i = 24; i >= 0; --i){
            if(hash[i] == hash[25]) ++ minLen;
        }
        //当所有X预占的位置插满了怎么办？
        //在任意插满区间（这里是ABC）后面按序插入剩余元素，比如ABCD ABCD发现D之间距离至少为n+1，肯定满足冷却条件
        //因此，当X预占位置能插满时，最短序列长度就是task.length，不能插满则取最少预占序列长度
        return Math.max(minLen, tasks.length);
    }



}
